/**
 * Utilities library
 *
 * @author nasca iacob
 * @version 1.0.0
 *
 * @classes CookieUtils
 */
window['Endian'] = { BIG : 0, LITTLE : 1};

namespace("jclass.utils")
({
    CookiesUtils : jClass({
        constructor : function(cookiePath, secure, cookieDomain) {
            path = cookiePath || "/";
            secure = typeof secure !== 'undefined' ? secure : false;
            domain = cookieDomain;
            loadCookies.apply(this);
        },
        private : {
            secure : false, domain : window.location.hostname, cookie : '', path   : "/", oldValues : {},

            loadCookies : function() {
                var cookies = document.cookie.split("; ");
                for (var i = 0; i < cookies.length; i++) {
                    var cookieName = cookies[i].split("=")[0];
                    var cookieValue = cookies[i].split("=")[1];
                    bindCookie.apply(this, [cookieName, cookieValue])
                }
                if (IE) {
                    var target = this;
                    function cookieCheck() {
                        checkCookieValues.call(target);
                    }
                    setInterval(cookieCheck, 100);
                }
            },
            bindCookie : function(cookieName, cookieValue) {
                if (IE) {
                   oldValues[cookieName] = cookieValue;
                   this[cookieName] = cookieValue;
                } else {
                   if (Object.prototype.__lookupGetter__.call(this,cookieName) == undefined) {
                       Object.prototype.__defineGetter__.call(this, cookieName, readCookieValue(cookieName));
                       Object.prototype.__defineSetter__.call(this, cookieName, setCookieValue(cookieName));
                   }
                }
            },
            checkCookieValues : function() {
                for (var item in oldValues) {
                    if (this[item] != oldValues[item]) {
                        this.addCookie(item, this[item]);
                    }
                    if (this[item] != this.getCookie(item)) {
                        this[item] = oldValues[item] = this.getCookie(item);
                    }
                }
            },
            readCookieValue : function(name) {
                return function() {
                    return this.getCookie(name);
                }
            },
            setCookieValue : function(name) {
                return function(val) {
                    this.addCookie(name, val);
                }
            }
        },
        public : {
            addCookie : function(cookieName, cookieValue, cookieExpiration) {
                var expires = '';
                if(!isNaN(cookieExpiration)) {
                    var date = new Date();
                    date.setTime(date.getTime() + cookieExpiration * 24 * 60 * 60 * 1000);
                    expires = "; expires=" + date.toGMTString();
                }

                document.cookie = cookieName + "=" + escape(cookieValue) + expires + "; path=" + path;
                bindCookie.apply(this, [cookieName, cookieValue]);
            },
            getCookie : function(cookieName) {
                if (this.hasCookie(cookieName)) {
                    return unescape(cookie.substring(cookieName.length + 1, cookie.length).replace(';', '')) || undefined;
                }
                return undefined;
            },
            hasCookie : function(cookieName) {
                tmp =  document.cookie.match((new RegExp(cookieName +'s*=s*(.*?)(;|$)')));
		        if(!tmp || !tmp[0]) {
                    return false;
                }
                cookie = tmp[0];
                return true;
            },
            deleteCookie : function(cookieName) {
                this.addCookie(cookieName, '', -1);
                if (IE) {
                    delete(oldValues[cookieName])
                }
                delete(this[cookieName]);
            }
        }
    }),

    ByteArray : jClass({
        constructor : function(byteData, endianType) {
            if (byteData) {
                _bytes = byteData || '';
                _endian = endianType || _endian;
                _len = data.length;
            }
            _pos = 0;
            var b = function(bigEndian, buffer){
                this.bigEndian = bigEndian, this.buffer = [], this.setBuffer(buffer);
            };
            b.prototype.readBits = function(start, length){
                //shl fix: Henri Torgemane ~1996 (compressed by Jonas Raoni)
                function shl(a, b){
                    for(++b; --b; a = ((a %= 0x7fffffff + 1) & 0x40000000) == 0x40000000 ? a * 2 : (a - 0x40000000) * 2 + 0x7fffffff + 1);
                    return a;
                }
                if(start < 0 || length <= 0)
                    return 0;
                this.checkBuffer(start + length);
                for(var offsetLeft, offsetRight = start % 8, curByte = this.buffer.length - (start >> 3) - 1,
                    lastByte = this.buffer.length + (-(start + length) >> 3), diff = curByte - lastByte,
                    sum = ((this.buffer[ curByte ] >> offsetRight) & ((1 << (diff ? 8 - offsetRight : length)) - 1))
                    + (diff && (offsetLeft = (start + length) % 8) ? (this.buffer[ lastByte++ ] & ((1 << offsetLeft) - 1))
                    << (diff-- << 3) - offsetRight : 0); diff; sum += shl(this.buffer[ lastByte++ ], (diff-- << 3) - offsetRight)
                );
                return sum;
            };
            b.prototype.setBuffer = function(data){
                if(data){
                    for(var l, i = l = data.length, b = this.buffer = new Array(l); i; b[l - i] = data.charCodeAt(--i));
                    !this.bigEndian && b.reverse();
                }
            };
            b.prototype.hasNeededBits = function(neededBits){
                return this.buffer.length >= -(-neededBits >> 3);
            };
            b.prototype.checkBuffer = function(neededBits){
                if(!this.hasNeededBits(neededBits))
                    throw new Error("checkBuffer::missing bytes");
            };
            Buffer = b;
        },
        private : {
             Buffer : '',
            _bytes : '',
            _len : 0,
            _pos : 0,
            _endian : 0,
            warn : function(msg){
                console.log(msg);
                throw new Error(msg);
            },
            encodeFloat : function(number, precisionBits, exponentBits) {
                var bias = Math.pow(2, exponentBits - 1) - 1,
                    minExp = -bias + 1,
                    maxExp = bias,
                    minUnnormExp = minExp - precisionBits,
                    status = isNaN(n = parseFloat(number)) || n == -Infinity || n == +Infinity ? n : 0,
                    exp = 0,
                    len = 2 * bias + 1 + precisionBits + 3,
                    bin = new Array(len),
                    signal = (n = status !== 0 ? 0 : n) < 0, n = Math.abs(n), intPart = Math.floor(n), floatPart = n - intPart,
                    i, lastBit, rounded, j, result, r;
                for(i = len; i; bin[--i] = 0) {}
                for(i = bias + 2; intPart && i; bin[--i] = intPart % 2, intPart = Math.floor(intPart / 2)) {}
                for(i = bias + 1; floatPart > 0 && i; (bin[++i] = ((floatPart *= 2) >= 1) - 0) && --floatPart) {}
                for(i = -1; ++i < len && !bin[i];) {}
                if(bin[(lastBit = precisionBits - 1 + (i = (exp = bias + 1 - i) >= minExp && exp <= maxExp ? i + 1 : bias + 1 - (exp = minExp - 1))) + 1]){
                    if(!(rounded = bin[lastBit]))
                        for(j = lastBit + 2; !rounded && j < len; rounded = bin[j++]) {}
                    for(j = lastBit + 1; rounded && --j >= 0; (bin[j] = !bin[j] - 0) && (rounded = 0)) {}
                }
                for(i = i - 2 < 0 ? -1 : i - 3; ++i < len && !bin[i];) {}

                (exp = bias + 1 - i) >= minExp && exp <= maxExp ? ++i : exp < minExp &&
                    (exp != bias + 1 - len && exp < minUnnormExp && this.warn("encodeFloat::float underflow"), i = bias + 1 - (exp = minExp - 1));
                (intPart || status !== 0) && (warn(intPart ? "encodeFloat::float overflow" : "encodeFloat::" + status),
                    exp = maxExp + 1, i = bias + 2, status == -Infinity ? signal = 1 : isNaN(status) && (bin[i] = 1));
                for(n = Math.abs(exp + bias), j = exponentBits + 1, result = ""; --j; result = (n % 2) + result, n = n >>= 1) {}
                for(n = 0, j = 0, i = (result = (signal ? "1" : "0") + result + bin.slice(i, i + precisionBits).join("")).length, r = [];
                    i; n += (1 << j) * result.charAt(--i), j == 7 && (r[r.length] = String.fromCharCode(n), n = 0), j = (j + 1) % 8) {}
                r[r.length] = n ? String.fromCharCode(n) : "";
                return _endian == Endian.BIG ? r.reverse() : r;
            },
            decodeFloat : function(data, precisionBits, exponentBits){
                var b = ((b = new Buffer(_endian, data)).checkBuffer(precisionBits + exponentBits + 1), b),
                    bias = Math.pow(2, exponentBits - 1) - 1, signal = b.readBits(precisionBits + exponentBits, 1),
                    exponent = b.readBits(precisionBits, exponentBits), significand = 0,
                    divisor = 2, curByte = b.buffer.length + (-precisionBits >> 3) - 1,
                    byteValue, startBit, mask;

                do for(byteValue = b.buffer[ ++curByte ], startBit = precisionBits % 8 || 8, mask = 1 << startBit;
                        mask >>= 1; (byteValue & mask) && (significand += 1 / divisor), divisor *= 2) {}
                while(precisionBits -= startBit);
                return exponent == (bias << 1) + 1 ? significand ? NaN : signal ? -Infinity : +Infinity
                    : (1 + signal * -2) * (exponent || significand ? !exponent ? Math.pow(2, -bias + 1) * significand
                    : Math.pow(2, exponent - bias) * (1 + significand) : 0);
            }
        },
        public : {

            position : GetSet(function() { return _pos; }, function(val) { _pos = val; }),
            bytesAvailable : Get(function() { return _len - _pos; }),
            length : Get(function() { return _len; }),
            endian : GetSet(function() { return _endian; }, function(val) { _endian = val; }),

            data : GetSet(function() { return _bytes; }, function(val) { _bytes = val || ''; _len = _bytes.length; }),

            clear : function() {
                this.position = 0;
                this.data = '';
            },
            
            readBoolean : function() {
                if (this.bytesAvailable === 0) { warn("readBoolean::End of stream!"); }
                return (_bytes.charCodeAt(_pos++) & 0xFF) ? true : false;
            },
            writeBoolean : function(val) {
                this.writeByte(val ? 0x01 : 0x00);
            },

            readUnsignedByte : function() { // AS name
                if (this.bytesAvailable === 0) { warn("readUnsignedByte::End of stream!"); }
                return (_bytes.charCodeAt(_pos++) & 0xFF);
            },
            readByte : function() {
                if (this.bytesAvailable === 0) { warn("readByte::End of stream!"); }
                return (_bytes.charCodeAt(_pos++) & 0xFF);
            },
            writeByte : function(val) {
                if (_pos < length) {
                    _bytes = _bytes.substr(0, _pos) + String.fromCharCode(val & 0xFF) + _bytes.substring(_pos + 1);
                } else {
                    _bytes += String.fromCharCode(val & 0xFF);
                    _len += 1;
                }
                _pos++;
            },

            readBytes : function(offset, length) {
                return _bytes.substr(offset, length);
            },
            writeBytes : function(bytes) {
                var bytesList = bytes.split('');
                for (var i = 0; i < bytes.length; i++) {
                    this.writeByte(bytes[i].charCodeAt());
                }
            },

            readDouble : function() {
               if (this.bytesAvailable < 8) { warn("readDouble::End of stream!"); }
                _pos += 8;
                return decodeFloat(_bytes.substr(_pos - 8, 8), 52, 11);
            },
            writeDouble : function(val) {
                var bytes = encodeFloat(val, 52, 11);
                for (var x = 0; x < 8; x++) {
                    this.writeByte(bytes[x].charCodeAt());
                }
            },

            readFloat : function() {
                if (this.bytesAvailable < 4) { warn("readFloat::End of stream!"); }
                _pos += 4;
                return decodeFloat(_bytes.substr(_pos - 4, 4), 23, 8);
            },
            writeFloat : function(number) {
                var bytes = encodeFloat(number, 23, 8);
                for (var x = 0; x < 4; x++) {
                    this.writeByte(bytes[x].charCodeAt());
                }
            },

            readUnsignedInt : function() {
                if (this.bytesAvailable < 4) { throw new Error("End of stream!"); }
                var d = _bytes, p = 0, x = 0;
                if (_endian == Endian.BIG) {
                     p = (_pos += 4) - 4;
                     x =  ((d.charCodeAt(p) & 0xFF) << 24) |
                            ((d.charCodeAt(++p) & 0xFF) << 16) |
                            ((d.charCodeAt(++p) & 0xFF) << 8) |
                            (d.charCodeAt(++p) & 0xFF);
                } else {
                    p = (_pos += 4);
                    x =  ((d.charCodeAt(--p) & 0xFF) << 24) |
                            ((d.charCodeAt(--p) & 0xFF) << 16) |
                            ((d.charCodeAt(--p) & 0xFF) << 8) |
                            (d.charCodeAt(--p) & 0xFF);
                }
                return x;
            },
            readInt : function() {
                if (this.bytesAvailable < 4) { throw new Error("End of stream!"); }
                var d = _bytes, p = 0, x = 0;
                if (_endian == Endian.BIG) {
                    p = (_pos += 4) - 4;
                    x = ((d.charCodeAt(p) & 0xFF) << 24) | ((d.charCodeAt(++p) & 0xFF) << 16) | ((d.charCodeAt(++p) & 0xFF) << 8) | (d.charCodeAt(++p) & 0xFF);
                } else {
                    p = (_pos += 4);
                    x = ((d.charCodeAt(--p) & 0xFF) << 24) | ((d.charCodeAt(--p) & 0xFF) << 16) | ((d.charCodeAt(--p) & 0xFF) << 8) | (d.charCodeAt(--p) & 0xFF);
                }
                return (x >= 2147483648) ? x - 4294967296 : x;
            },
            writeInt : function(val) {
                 if (_endian == Endian.BIG) {
                     this.writeByte((val >> 24) & 0xFF);
                     this.writeByte((val >> 16) & 0xFF);
                     this.writeByte((val >> 8) & 0xFF);
                     this.writeByte((val) & 0xFF);
                 } else {
                     this.writeByte((val) & 0xFF);
                     this.writeByte((val >> 8) & 0xFF);
                     this.writeByte((val >> 16) & 0xFF);
                     this.writeByte((val >> 24) & 0xFF);
                 }
            },

            readUnsignedShort : function() {
                if (this.bytesAvailable < 2) { throw new Error("End of stream!"); }
                var d = _bytes, p = 0;
                if (_endian == Endian.BIG) {
                    p = (_pos += 2) - 2;
                    return ((d.charCodeAt(p) & 0xFF) << 8) | (d.charCodeAt(++p) & 0xFF);
                } else {
                    p = (_pos += 2);
                    return ((d.charCodeAt(--p) & 0xFF) << 8) | (d.charCodeAt(--p) & 0xFF);
                }
            },
            readShort : function() {
                if (this.bytesAvailable < 2) { throw new Error("End of stream!"); }
                var d = _bytes, p = 0, x = 0;
                if (_endian == Endian.BIG) {
                    p = (_pos += 2) - 2;
                    x = ((d.charCodeAt(p) & 0xFF) << 8) | (d.charCodeAt(++p) & 0xFF);
                } else {
                    p = (_pos += 2);
                    x = ((d.charCodeAt(--p) & 0xFF) << 8) | (d.charCodeAt(--p) & 0xFF);
                }
                return (x >= 32768) ? x - 65536 : x;
            },
            writeShort : function(val) {
                 if (_endian == Endian.BIG) {
                     this.writeByte((val >> 8) & 0xFF);
                     this.writeByte((val) & 0xFF);
                 } else {
                     this.writeByte((val) & 0xFF);
                     this.writeByte((val >> 8) & 0xFF);
                 }
            },
            readUTF : function() {
                var l = this.readShort(), output = "";
                for (var i = 0; i < l; i++) {
                    output += String.fromCharCode(this.readByte());
                }
                return output;
            },
            writeUTF : function(val) {
                this.writeShort(val.length);
                this.writeBytes(val);
            },

            readUTFBytes : function(readLength) {
                readLength = readLength || 0;
                var output = '';
                for (var i = 0; i < readLength; i++) {
                    output += String.fromCharCode(this.readByte());
                }
                return output;
            }
        }
    })
});